import { error } from "console";
import redis = require("redis");
import { isUint16Array } from "util/types";

/** redis连接配置 */
export interface RedisConfig {
    /**是否有ssl */
    ssl: boolean;
    /**主机地址,ip或者域名 */
    host: string;
    /**端口 */
    port: number;
    /** 用户名, 没有认证放空 */
    username: string;
    /** 密码, 没有认证或密码放空, 有的话一定要有username,否则无效 */
    password: string;
    /**使用的数据库索引,一般默认使用0 */
    database: number;
}

/**redis客户端封装 */
export class RedisClient {
    private config: RedisConfig;
    client: redis.RedisClientType<redis.RedisModules, redis.RedisScripts>;
    /**
     * 初始化
     * @param cfg 配置对象
     */
    private constructor(cfg: RedisConfig) {
        this.config = cfg;
        //redis[s]://[[username][:password]@][host][:port][/db-number]:
        var url = "redis" + (this.config.ssl ? 's' : '') + "://";
        if (this.config.username) {
            url += this.config.username;
            if (this.config.password) {
                url += ":" + this.config.password;
            }
            url += "@";
        }
        url += this.config.host + ':' + this.config.port
            + '/' + this.config.database;
        this.client = redis.createClient({
            url: url
        });
    }

    /**
     * 创建连接好的客户端,建议全局静态一个
     * @param cfg 
     * @returns 
     */
    public static async createClient(cfg: RedisConfig): Promise<RedisClient> {
        var rc = new RedisClient(cfg);
        await rc.client.connect();
        return rc;
    }

    /**
     * 设置键值对,值是字符串
     * @param key 
     * @param val 
     * @param exTimeSec 在几秒后过期,0表示永不过期
     */
    public async setString(key: string, val: string, exTimeSec: number = 0): Promise<void> {
        var opt: any = {};
        if (exTimeSec) {
            opt.EX = exTimeSec;
        }
        this.client.hSet(opt, "", "a", "");
        await this.client.set(key, val, opt);
    }
    /**
     * 获取 @see setString 设置的值
     * @param key 
     * @returns 
     */
    public async getString(key: string): Promise<string | null> {
        return await this.client.get(key);
    }

    /**
     * 设置键值对,值类型是对象
     * @param key 
     * @param val 
     * @param exTimeSec 在几秒后过期,0表示永不过期
     */
    public async setObject(key: string, val: any, exTimeSec: number = 0): Promise<void> {
        var valJson: any = null;
        if (val) {
            valJson = JSON.stringify(val);
        }
        await this.setString(key, valJson, exTimeSec);
    }
    /**
     * 获取 @see setObject 设置的值
     * @param key 
     * @returns 
     */
    public async getObject(key: string): Promise<any | null> {
        var json = await this.getString(key);
        if (!json) return null;
        try {
            return JSON.parse(json);
        } catch (ex) {
            error('getObject("' + key + '")json解析失败:', json);
            return null;
        }
    }


    /**
     * 设置hash表的字段为对象值（会被序列化为json字符串进行存储）
     * @date 2022/4/20 - 16:13:15
     *
     * @public
     * @async
     * @param {string} key
     * @param {string} field
     * @param {*} valueObject 对象类型
     * @returns {Promise<void>}
     */
    public async setHashObject(key: string, field: string, valueObject: any): Promise<void> {
        var valJson: any = null;
        if (valueObject) {
            valJson = JSON.stringify(valueObject);
        }
        await this.setHashString(key, field, valJson);
    }
    /**
     * 设置hash表的字段为字符串值
     * @date 2022/4/20 - 16:13:15
     *
     * @public
     * @async
     * @param {string} key
     * @param {string} field
     * @param {*} valueString 字符串类型
     * @returns {Promise<void>}
     */
    public async setHashString(key: string, field: string, valueString: string): Promise<number> {
        return await this.client.hSet(key, field, valueString);
    }

    
    /**
     * 获取哈希表里的所有键值对，字段值为对象类型
     * @date 2022/4/20 - 16:44:48
     *
     * @public
     * @async
     * @template ValueType
     * @param {string} key
     * @returns {Promise<{ [key: string]: ValueType }>}
     */
    public async getHashObjects<ValueType>(key: string): Promise<{ [key: string]: ValueType }> {
        var kv = await this.client.hGetAll(key);
        var ret: { [key: string]: ValueType } = {};
        for (var key in kv) {
            var json = kv[key];
            ret[key] = JSON.parse(json);
        }
        return ret;
    }
    /**
     * 获取哈希表里的所有键值对，字段值为字符串类型
     * @date 2022/4/20 - 16:44:48
     *
     * @public
     * @async
     * @template ValueType
     * @param {string} key
     * @returns {Promise<{ [key: string]: ValueType }>}
     */
    public async getHashValues(key: string): Promise<{ [key: string]: string }> {
        return await this.client.hGetAll(key);
    }

    /**
     * 删除hash表的字段
     * @public
     * @async
     * @param {string} key
     * @param {string} field
     * @returns {Promise<void>}
     */
    public async removeHashValue(key: string, field: string): Promise<void> {
        await this.client.hDel(key, field);
    }

    public dispose() {
        try {
            this.client.disconnect();
        } catch (e) { }
    }
}

var allRedisClients: { [key: string]: RedisClient } = {};
var allRedisCfg: { [key: string]: RedisConfig } = {};

/**
 * 初始化redis客户端
 * @param cfg 
 * @param configKey 根据这配置标识区分客户端
 */
export async function initRedisClient(cfg: RedisConfig, configKey: string = "default"): Promise<void> {
    allRedisCfg[configKey] = cfg;
    var existsClient = allRedisClients[configKey];
    if (existsClient) existsClient.dispose();
    allRedisClients[configKey] = await RedisClient.createClient(cfg);
}
/**
 * 获取初始化过的redis客户端
 * @param configKey 初始化时传入的,如果没传使用默认值,这里一样不传使用默认值
 * @returns 
 */
export async function getRedisClient(configKey: string = "default"): Promise<RedisClient> {
    var client = allRedisClients[configKey];
    if (client && client.client.isOpen) return client;
    var cfg = allRedisCfg[configKey];
    client = await RedisClient.createClient(cfg);
    allRedisClients[configKey] = client;
    return client;
}